/**
 * 
 */
package fileServer;



import filePackets.*;
import java.net.*;
import java.io.*;
import java.util.TreeMap;

import server.IServerApplication;
import server.Logger;
import server.SAExn_TaskIdAlreadyPresent;
import server.SAExn_UnexpectedTaskId;
import server.SAIncramentalTaskContext;
import server.SATaskContext;
import server.ServerApplication;

import cmm.CMMExn_ClusterErr;
import cmm.ClusterManagementModule;
import cmm.ConnectionInformation;
import cmm.IncramentalTaskContext;


/**
 * @author peleg
 *
 */
public class FileServerApplication extends ServerApplication implements IServerApplication {
	/* ----------- private declerations ------------------------- */
	private static ClusterManagementModule myCMM;
	private static ConnectionInformation myConnectionInformation;
	private static ConnectionInformation proxyConnectionInformation;
//	private static TreeMap<Integer, SATaskContext> TaskContextDB;
	private static FileTaskContext newTaskContext;
	private static Integer newTaskId;
	private static boolean newTaskContextIsValid;
	private static InetAddress LocalAddr;
	private static DatagramSocket Socket;
	private static Logger LogFile;
	
	/* ------------ constant definitions ------------------------- */ 
	public static final int DGRAM_BUF_LEN = 4096; 
	private static final short DEFAULT_PORT_NUM = 2048;
	private static final String LOG_FILENAME = "ServerApp.log";
	private static final String MyString = "FileServer";
	private static final String PROXY_HOST = "Localhost"; 
	private static final short PROXY_PORT = 4096;
	private static final String ClusterGroupName = "FileServerCluster";
	private static final String SpreadConfigFile = "spread.cfg";
	
	public void UpdateTask()
	/* ------------ function implementations --------------------- */
/*	not needed, AFAIK
	private static byte[] ConvertObjectToBytes(Object obj) throws java.io.IOException{
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(bos);
		oos.writeObject(obj);
		oos.flush();
		oos.close();
		bos.close();
		byte [] data = bos.toByteArray();
		return data;
	}
	
	private static Object ConvertBytesToObject(byte[] buf) throws java.io.IOException, ClassNotFoundException{
		
		ByteArrayInputStream bis = new ByteArrayInputStream(buf);
		ObjectInputStream ois = new ObjectInputStream(bis);
		Object obj = ois.readObject();
		bis.close();
		ois.close();
		
		return obj;
	}
*/	
	public static void LogEntry(String ModuleName, String Entry){
		LogFile.AddEntry(ModuleName, Entry);		
	}
				
	/* (non-Javadoc)
	 * @see server.IServerApplication#ReceiveNewTaskId(int)
	 */
	public void ReceiveNewTaskId(Integer TaskId) throws SAExn_UnexpectedTaskId, SAExn_TaskIdAlreadyPresent {
		if (!newTaskContextIsValid){
			SAExn_UnexpectedTaskId excpt = new SAExn_UnexpectedTaskId(TaskId);
			throw excpt; 
		}
		newTaskId = TaskId;
		newTaskContextIsValid = false;
		return;
	}

	/* (non-Javadoc)
	 * @see server.IServerApplication#ReconstructTaskContext(server.TaskContext, server.IncramentalTaskContext)
	 */
	public SATaskContext ReconstructTaskContext(SATaskContext OldContext,
			IncramentalTaskContext Delta) {
		// TODO Auto-generated method stub
		return null; // TODO figure out how I can cast Delta to TaskContext type;
	}
	
	public FileServerApplication(){
//		 prepeare socket
		LogFile = new Logger();
		try{
			LogFile.OpenLog(LOG_FILENAME);
		}
		catch (FileNotFoundException e){
			System.err.println("Error opening log file !");
			return;// TODO: in order to quit the app, do I need to throw some kind of exception ?
		}
		try {
		    Socket = new DatagramSocket(DEFAULT_PORT_NUM);
		} catch (SocketException e) {
			System.err.println("Error creating datagram socket");
		    e.printStackTrace();
            return;
		}
				
		// obtain local IP address to report to CMM
		try {
			LocalAddr = InetAddress.getLocalHost();
		}
		catch (IOException e) {
 			System.err.println("Error obtaining local IP address");
 			System.err.println(e);
 			return;
		}
				myConnectionInformation = new ConnectionInformation();
		myConnectionInformation.putIP(LocalAddr);
		myConnectionInformation.putPort(DEFAULT_PORT_NUM);
		
		proxyConnectionInformation = new ConnectionInformation();
		try {
			proxyConnectionInformation.putIP(InetAddress.getByName(PROXY_HOST));
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		proxyConnectionInformation.putPort(PROXY_PORT);

		/* create the CMM */
		try {
			//
			myCMM = new ClusterManagementModule(this, myConnectionInformation, proxyConnectionInformation, ClusterGroupName, SpreadConfigFile );
		} catch (CMMExn_ClusterErr e1) {
			LogFile.AddEntry(MyString, "got CMMExn_ClusterErr on CMM contructor run");
			e1.printStackTrace();
		} catch (FileNotFoundException e2){
			LogFile.AddEntry(MyString, "got FileNotFoundException on CMM contructor run");
			e2.printStackTrace();			
		} catch (UnknownHostException e3) {
			LogFile.AddEntry(MyString, "FileServer got UnknownHostException when starting CMM");
			e3.printStackTrace();
		}
	}

	/* (non-Javadoc)
	 * @see server.IServerApplication#StopServicingTask(int)
	 */
	public void StopServicingTask(int TaskId) {
		// TODO implement when we add support for task migration
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		newTaskContext = new FileTaskContext();
		newTaskId = new Integer(0);
		newTaskContextIsValid = false;
		byte[] buf = new byte[DGRAM_BUF_LEN];
		DatagramPacket packet = new DatagramPacket(buf, buf.length);
		ConnectionInformation newConn = new ConnectionInformation();
		NewSessionPacket IncomingPacket;
	
						
		while (true){
			try{
				Socket.receive(packet);
			}
			catch (IOException e) {
				System.err.println("Error receiving on server socket");
				return;
			}
			
			try {
				IncomingPacket = new NewSessionPacket(packet.getData());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return;
				
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return;
			}
			if (IncomingPacket.getPacketType() != FilePacket.PacketType.NEW_SESSION){
				LogFile.AddEntry(MyString, "Error: server only expects NEW SESSION packets");
				return;				
			}
			// prepare connection information for the task context
			newConn.putIP(IncomingPacket.getClientData().getIP());
			newConn.putPort(IncomingPacket.getClientData().getPort());
			
			// prepare new task context
			newTaskContext.setConnInfo(newConn);
			newTaskContext.setBlockNumber(0);
			newTaskContext.setFileNumber(IncomingPacket.getRequestedFile());
			newTaskContextIsValid = true;
						
			try {
				myCMM.AppNewTask(newTaskContext);
			} catch (CMMExn_ClusterErr e) {
				LogFile.AddEntry(MyString, "Error: CMM reports a cluster error");
				e.printStackTrace();
				return;
			}
			// wait for new task ID to arrive
			while (newTaskContextIsValid) {
				try {
					Thread.sleep(10); // sleep 10 ms
				} catch (InterruptedException e) {
					LogFile.AddEntry(MyString, "Error: unexpected interrupt on sleep");
					e.printStackTrace();
				}
			}
				
			// TODO: start the new thread here
			
		}
		

	}

	public void AddLogEntry(String ModuleName, String Entry) {
		// TODO Auto-generated method stub
		
	}

	public void DropNewTask() { // called in case the CMM can't provide a new task ID
		// TODO Auto-generated method stub
		
	}

	public SATaskContext ReconstructTaskContext(SATaskContext OldContext, SAIncramentalTaskContext Delta) {
		// TODO Auto-generated method stub
		return null;
	}

	public void StartServicingTask(Integer TaskId, SATaskContext Task) {
		// TODO Auto-generated method stub
		
	}

	public void StopServicingTask(Integer TaskId) {
		// TODO Auto-generated method stub
		
	}

	public void UnrecoverableClusterError() {
		// TODO Auto-generated method stub
		
	}

}
